package com.coder.rental.security;

import cn.hutool.core.util.StrUtil;
import cn.hutool.jwt.JWTPayload;
import com.coder.rental.utils.JwtUtils;
import com.coder.rental.utils.RedisUtils;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;


import java.io.IOException;

/**
 * oncePerRequestFilter是springboot 提供的一个过滤器,是个抽象类
 * 在springSecurity应用广泛 可以用于过滤请求
 * 这个过滤器主要用于: 继承实现并在每次请求时执行
 */
@Component
public class VerifyTokenFilter extends OncePerRequestFilter {

    @Value("${request.login-url}")
    private String loginUrl;

    @Autowired
    private RedisUtils redisUtils;

    @Autowired
    private CustomerUserDetailService customerUserDetailService;
    @Autowired
    private LoginFailureHandler loginFailureHandler;

    /**
     * 处理过滤逻辑,用于验证请求的令牌
     * 如果请求令牌URL不是登录页面,则进行令牌验证
     * 如果验证失败,则调用失败处理器
     *
     * @param request
     * @param response
     * @param filterChain
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        StringBuffer url = request.getRequestURL();
        //如果url地址和yml里面的地址不一样(也就是/rental/user/login)登录页面,则直接过滤掉
        if (!StrUtil.equals(url,loginUrl)){
            //校验token是否为空
            try {
                validateToken(request,response);
            }catch (AuthenticationException e){
                //登录失败处理器
                loginFailureHandler.onAuthenticationFailure(request,response,e);
            }
        }
        doFilter(request,response,filterChain);
    }

    /**
     * 验证令牌（token）的有效性。
     *
     * @param request HttpServletRequest对象，用于获取请求头或参数中的token。
     * @param response HttpServletResponse对象，用于在验证失败时设置响应状态码。
     * @throws AuthenticationException 如果token验证失败，抛出CustomerAuthenticationException异常。
     */
    private void validateToken(HttpServletRequest request,
                               HttpServletResponse response) throws AuthenticationException{
        //校验token
        // 从请求头中获取token:token是存放在请求头中的
        String token = request.getHeader("token");
        //判断请求头中的token是否为空
        if (StrUtil.isEmpty(token)){
            //如果为空,从请求中获取参数:有些tokens是放在参数中的,所以请求获取token
            token = request.getParameter("token");
        }
        if (StrUtil.isEmpty(token)){
            //参数和请求头中token都为空,则抛出异常
            throw new CustomerAuthenticationException("token为空");
        }

        //如果token不为空,则判断是否和Redis中存储的token是否一致
        String tokenValue = redisUtils.get("token:token");
        System.out.println(tokenValue);
        if (StrUtil.isEmpty(tokenValue)){
            //Redis数据库中的Redis为空,则表示token过期
            throw new CustomerAuthenticationException("token值已过期");
        }
        if (!StrUtil.equals(token,tokenValue)){
            //判断存储在Redis数据中的token是否和当前token一致
            throw new CustomerAuthenticationException("token验证失败");
        }

        //解析token中的数据,获取到用户名
        String username = JwtUtils.parseToken(token).getClaim("username").toString();
        if (StrUtil.isEmpty(username)){
            //如果解析失败则抛出异常
            throw new CustomerAuthenticationException("token解析失败");
        }
        //获取并封装用户的所有新信息(包括权限列表和子权限)
        UserDetails userDetails = customerUserDetailService.loadUserByUsername(username);
        if (userDetails==null){
            //如果为空,则表示用户不存在
            throw new CustomerAuthenticationException("用户不存在");
        }

        //创建并设置认证信息
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken=
                new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());
        usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource()
                .buildDetails(request));
        //将认证信息设置到SecurityContextHolder环境中
        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
    }
}